<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  

  
  <title>Android自定义View——贝赛尔曲线 | 海晨忆的博客</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  
  
  
  <meta name="description" content="&amp;emsp;&amp;emsp;本文针对有一定自定义View的童鞋，最好对贝赛尔曲线有辣么一丢丢了解，不了解也没关系。花5分钟看一下 GcsSloop的 安卓自定义View进阶-Path之贝塞尔曲线。 本文的最终效果图：">
<meta property="og:type" content="article">
<meta property="og:title" content="Android自定义View——贝赛尔曲线">
<meta property="og:url" content="https://haichenyi.com/2018/05/13/Android自定义View——贝赛尔曲线/index.html">
<meta property="og:site_name" content="海晨忆的博客">
<meta property="og:description" content="&amp;emsp;&amp;emsp;本文针对有一定自定义View的童鞋，最好对贝赛尔曲线有辣么一丢丢了解，不了解也没关系。花5分钟看一下 GcsSloop的 安卓自定义View进阶-Path之贝塞尔曲线。 本文的最终效果图：">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://upload-images.jianshu.io/upload_images/7041675-bb761f08bb5704c5.gif?imageMogr2/auto-orient/strip">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2018-03-07/一个水波纹效果图.png">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2018-03-07/一个水波纹放入坐标系中.png">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2018-03-07/整个屏幕的水波纹效果图.png">
<meta property="og:image" content="https://upload-images.jianshu.io/upload_images/7041675-d38f9817410a015c.gif?imageMogr2/auto-orient/strip">
<meta property="og:image" content="https://upload-images.jianshu.io/upload_images/7041675-f84524374a6c4273.gif?imageMogr2/auto-orient/strip">
<meta property="og:image" content="https://upload-images.jianshu.io/upload_images/7041675-aefa92a1ca8a1026.gif?imageMogr2/auto-orient/strip">
<meta property="og:image" content="https://upload-images.jianshu.io/upload_images/7041675-54f3ea96e5dc33a5.gif?imageMogr2/auto-orient/strip">
<meta property="og:image" content="https://upload-images.jianshu.io/upload_images/7041675-bb761f08bb5704c5.gif?imageMogr2/auto-orient/strip">
<meta property="og:updated_time" content="2019-09-18T09:00:36.824Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Android自定义View——贝赛尔曲线">
<meta name="twitter:description" content="&amp;emsp;&amp;emsp;本文针对有一定自定义View的童鞋，最好对贝赛尔曲线有辣么一丢丢了解，不了解也没关系。花5分钟看一下 GcsSloop的 安卓自定义View进阶-Path之贝塞尔曲线。 本文的最终效果图：">
<meta name="twitter:image" content="https://upload-images.jianshu.io/upload_images/7041675-bb761f08bb5704c5.gif?imageMogr2/auto-orient/strip">
  
    <link rel="alternate" href="/atom.xml" title="海晨忆的博客" type="application/atom+xml">
  
  
    <link rel="icon" href="/uploads/artistic_image/head.jpg">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  <link rel="stylesheet" href="/css/style.css">
  <link rel="stylesheet" href="/css/highlight.css">
  <script>
  let antiquityStorage = window.sessionStorage.getItem('antiquitySessionStorage');
  if (antiquityStorage == '' || antiquityStorage == null) {
    var antiquityLoader = '<div id="loaderbox"><div class="loader"><div class="load-roll"><div class="load-top"></div><div class="load-right"></div><div class="load-bottom"></div></div></div></div>';
    document.write(antiquityLoader);
    document.body.style.overflow = 'hidden'
  }
  </script>
</head>

<body>
  <div id="fullpage" class="mobile-nav-right">
    
      <div id="wrapper" style="background-image: url(/uploads/artistic_image/bg.jpg)" title="背景图片来自网络">
    
    
      <header id="header">
  <div id="nav-toggle" class="nav-toggle"></div>
  <div class="head-box global-width">
    <nav class="nav-box nav-right">
      
        <a class="nav-item" href="/" title
        
        >首页</a>
      
        <a class="nav-item" href="/archives" title
        
        >归档</a>
      
        <a class="nav-item" href="/about" title
        
        >简历</a>
      
    </nav>
  </div>
</header>
      <div id="middlecontent" title class="global-width sidebar-left">
        <section id="main"><article id="post-Android自定义View——贝赛尔曲线" class="article global-container article-type-post" itemscope itemprop="blogPost">
  
    <header class="article-header">
      
  
    <h1 class="article-title" itemprop="name">
      Android自定义View——贝赛尔曲线
    </h1>
  

    </header>
  
  <div class="article-meta">
    <a href="/2018/05/13/Android自定义View——贝赛尔曲线/" class="article-date">
  <time datetime="2018-05-13T14:27:18.000Z" itemprop="datePublished">2018-05-13</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Android-自定义view/">Android -自定义view</a>
  </div>

    
  </div>
  
    <span id="busuanzi_container_page_pv">
      本文总阅读量<span id="busuanzi_value_page_pv"></span>次
    </span>
  

  <div class="article-inner">
    
    <div class="article-content article-content-cloud doorframe mac" itemprop="articleBody">
      
        <p>&emsp;&emsp;本文针对有一定自定义View的童鞋，最好对贝赛尔曲线有辣么一丢丢了解，不了解也没关系。花5分钟看一下 <strong><em>GcsSloop</em></strong>的 <a href="http://www.gcssloop.com/customview/Path_Bezier" target="_blank" rel="noopener">安卓自定义View进阶-Path之贝塞尔曲线</a>。</p>
<p>本文的最终效果图：</p>
<p><img src="https://upload-images.jianshu.io/upload_images/7041675-bb761f08bb5704c5.gif?imageMogr2/auto-orient/strip" alt="最终效果图.gif"></p>
<a id="more"></a>
<h2 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h2><ol>
<li>首先他是一个只有上半部分的正弦形状的水波纹，很规则。</li>
<li>其次，他这个正弦图左右在移动。</li>
<li>然后，就是它这个自定义View，上下也在移动，是慢慢增加的</li>
<li>最后，优化点：一开始刚出来的时候，它那个水波纹的角度，更达到一定角度后，最后面，快要完成的时候的角度是不一样的。</li>
</ol>
<h2 id="第一步：画正弦形状的水波纹"><a href="#第一步：画正弦形状的水波纹" class="headerlink" title="第一步：画正弦形状的水波纹"></a>第一步：画正弦形状的水波纹</h2><p>&emsp;&emsp;有一定自定义View基础的童鞋都知道，一阶贝赛尔画直线，这里的正弦图形是用二阶贝赛尔曲线。至于三阶，四阶，五阶用的都比较少。</p>
<p>&emsp;&emsp;我们这里知道了，这是用的二阶贝赛尔曲线，辣么，方法呢？</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">//不带r的方法是基于原点的坐标系(偏移量)， rXxx方法是基于当前点坐标系(偏移量)</span><br><span class="line">mPath.rQuadTo(dx1, dy1, dx2, dy2);</span><br><span class="line">//mPath.quadTo(dx1, dy1, dx2, dy2);</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;Path调用该方法，这里就是传的两个点，也就是四个值，参数的含义：第一个点是控制点，第二个点是终点。前面还有一个起点，通过</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mPath.moveTo(x,y);</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这个方法是确定起点。不懂的童鞋，看一下文章开头推荐的文章。我们效果的是一排波浪线，我们上面这个方法只是一个。举个例子：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">//构造方法里面初始化</span><br><span class="line">private void initView() &#123;</span><br><span class="line">    path = new Path();</span><br><span class="line">    paint = new Paint();</span><br><span class="line">    paint1 = new Paint();</span><br><span class="line">    paint.setColor(Color.GREEN);</span><br><span class="line">    paint.setStyle(Paint.Style.STROKE);</span><br><span class="line">    paint.setStrokeWidth(10);</span><br><span class="line">    paint1.set(paint);</span><br><span class="line">    paint1.setColor(Color.RED);</span><br><span class="line">  &#125;</span><br><span class="line">//onDraw里面去画出来</span><br><span class="line">    @Override</span><br><span class="line">  protected void onDraw(Canvas canvas) &#123;</span><br><span class="line">    super.onDraw(canvas);</span><br><span class="line">    path.moveTo(0,300);</span><br><span class="line">    path.quadTo(150, 150, 300, 300);</span><br><span class="line">    path.quadTo(450, 450, 600, 300);</span><br><span class="line">    canvas.drawPath(path, paint);</span><br><span class="line"></span><br><span class="line">    canvas.drawCircle(0,300,5,paint1);</span><br><span class="line">    canvas.drawCircle(150,150,5,paint1);</span><br><span class="line">    canvas.drawCircle(300,300,5,paint1);</span><br><span class="line">    canvas.drawCircle(450,450,5,paint1);</span><br><span class="line">    canvas.drawCircle(600,300,5,paint1);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;上面就是简单的初始化paint，和path，然后画出path，另外，我还画出了5个点，帮助理解。跑出来的效果图如下：</p>
<p><img src="/uploads/article/2018-03-07/一个水波纹效果图.png" alt="一个水波纹效果图.png"></p>
<p>&emsp;&emsp;转换成对应的坐标系，手画的，有点丑，知道是这个意思就行了。如下：</p>
<p><img src="/uploads/article/2018-03-07/一个水波纹放入坐标系中.png" alt="一个水波纹放入坐标系中.png"></p>
<p><strong>画一个正弦图的思路：</strong></p>
<ol>
<li><p>首先，把path移动到起点，对应的也就是moveTo(0,300)</p>
</li>
<li><p>然后，确定终点，也就是我们前面说的quadTo()方法的第二个点(300,300)</p>
</li>
<li><p>最后，我们确定控制点，也就是我们前面说的quadTo()方法的第一个点。 <strong><em>辣么，这个控制点是怎么确定的呢？问题就在这里。敲黑板</em></strong> 因为我们画的是一个规则的正弦图，所以，控制点的x坐标肯定是终点x坐标300的一半，也就是150。再就是他的y坐标，其实y坐标是随便定义的。y坐标只是约束这个正弦图形的坡度，对坡度。你把y坐标定义的离终点的y坐标远一点，他的坡度就大一点。离他近一点，坡度就小一点。你如果定义控制点是（150,100），他相对于控制点是(150,150)的坡度就会大一点。因为100距300相差200，150距300相差150。200大于150。对，就是这样。辣么，怎么控制是上半部分的正弦图还是下半部分的正弦图呢？ <strong><em>|y控|&gt;|y终|，上半部分；相反，则是下半部分。</em></strong></p>
</li>
</ol>
<p>&emsp;&emsp;对了，这里我需要说明的是，上面我们调用了两次quadTo()方法，第二次调用的起点，就是第一次的终点。</p>
<p>&emsp;&emsp;上面效果是调用quadTo()方法，我们再来说一说rQuadTo()方法。上面的注释里面，我们也标明了两者的区别。 <strong><em>辣么，什么叫相对于原点的坐标系？什么叫相对于当前点的坐标系呢？</em></strong> 我们知道android的坐标系原点是左上角，你可以这样理解，第一种，不带r的方法quadTo()，他的坐标原点(0,0)点始终在左上角，第二种带r的方法rQuadTo()，我们第一次移动到起点(0,300)的时候，这个时候的原点就是(0,300)，所以说此时的终点应该是(300,0),然后确定我们的控制点(150,-150)。辣么，我们调用第二次的时候，此时的终点就是(300,0)，这个时候的终点就是(300,0)，在确定此时的控制点(150,150)。两次的终点都是(300,0)，但是，意义是不一样的。有点绕，但是你理解了相对于原点坐标系，和相对于当前点的坐标系，就很简单了。理解一下，思考5分钟。辣么，上面用带r的怎么写呢？</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">path.moveTo(0,300);</span><br><span class="line">//    path.quadTo(150, 150, 300, 300);</span><br><span class="line">//    path.quadTo(450, 450, 600, 300);</span><br><span class="line">    path.rQuadTo(150,-150,300,0);</span><br><span class="line">    path.rQuadTo(150,150,300,0);</span><br><span class="line">    canvas.drawPath(path, paint);</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;至此，怎么换一个正弦图，以及，两个方法的区别，已经讲完了，我觉得已经讲的非常清楚了。感觉，没有谁比我讲的还要清楚了。手把手教学。我们这个效果，画一个，肯定不行。要画满一个屏幕。怎么画呢？</p>
<p>&emsp;&emsp;找规律，一个正弦图，我们上面都是围绕这三个点，起点，控制点，终点。要想规则，控制点的x坐标是终点x坐标的一半。再就是，要画满一个屏幕，要在屏幕内部，所以，终点x坐标要小于屏幕宽度。综上所述。</p>
<ol>
<li><p>三个点：起点，终点，控制点</p>
</li>
<li><p>控制点的x坐标是终点x坐标的一半</p>
</li>
<li><p>终点x坐标要小于屏幕宽度</p>
</li>
</ol>
<p>我们就开始写代码了：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">  private int startY = 300;//定义起始点的y坐标</span><br><span class="line"></span><br><span class="line">  private int endX = 300;//定义终点的x坐标</span><br><span class="line"></span><br><span class="line">  private int controlY = 150;//定义控制点的y坐标</span><br><span class="line">  </span><br><span class="line">  @Override</span><br><span class="line">  protected void onDraw(Canvas canvas) &#123;</span><br><span class="line">    super.onDraw(canvas);</span><br><span class="line">    path.moveTo(0, 300);</span><br><span class="line">    path.moveTo(0, startY);</span><br><span class="line">//    path.quadTo(150, 150, 300, 300);</span><br><span class="line">//    path.quadTo(450, 450, 600, 300);</span><br><span class="line">//这里的for循环为什么每次要加2倍的终点x坐标呢？</span><br><span class="line">    //你想一想，我们一次for循环，画的图的终点x坐标在哪？</span><br><span class="line">    for (int i = 0; i &lt; getWidth(); i += 2*endX) &#123;</span><br><span class="line">//      path.rQuadTo(150, -150, 300, 0);</span><br><span class="line">//      path.rQuadTo(150, 150, 300, 0);</span><br><span class="line">      path.rQuadTo(endX/2, -controlY, endX, 0);</span><br><span class="line">      path.rQuadTo(endX/2, controlY, endX, 0);</span><br><span class="line">    &#125;</span><br><span class="line">    canvas.drawPath(path, paint);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>效果图，如下：</p>
<p><img src="/uploads/article/2018-03-07/整个屏幕的水波纹效果图.png" alt="整个屏幕的水波纹效果图.png"></p>
<p>&emsp;&emsp;好，到这里，第一步完成了，满屏的水波纹出来了。</p>
<h2 id="第二步，正弦图左右在移动"><a href="#第二步，正弦图左右在移动" class="headerlink" title="第二步，正弦图左右在移动"></a>第二步，正弦图左右在移动</h2><p>&emsp;&emsp;想一想，这个动画，想一想，想一想，像不像水平位移动画？像不像？越想越像。辣么，我们就去验证一下。写一个动画，这种，明显就是属性动画。既然是左右移动，辣么就肯定是改变x轴的坐标值，改变谁的呢？肯定是起点的啊，只有改变起点的x左边的值，水波纹才会有动的效果</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">public void startAnimation()&#123;</span><br><span class="line">    ValueAnimator animator = ValueAnimator.ofFloat(0, 1);</span><br><span class="line">    animator.setDuration(1000);</span><br><span class="line">    animator.setRepeatCount(ValueAnimator.INFINITE);</span><br><span class="line">    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() &#123;</span><br><span class="line">      @Override</span><br><span class="line">      public void onAnimationUpdate(ValueAnimator animation) &#123;</span><br><span class="line">        Float animatedValue = (Float) animation.getAnimatedValue();</span><br><span class="line">        currentStartX = (int) (endX * animatedValue);</span><br><span class="line">        postInvalidate();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    animator.start();</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">   @Override</span><br><span class="line">  protected void onDraw(Canvas canvas) &#123;</span><br><span class="line">    super.onDraw(canvas);</span><br><span class="line">    path.reset();//不加会有重影，不是我们想要的</span><br><span class="line">//    path.moveTo(0, 300);</span><br><span class="line">//    path.moveTo(0, startY);</span><br><span class="line">    path.moveTo(currentStartX, startY);</span><br><span class="line">      ...//其他的不变</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>效果图如下：</p>
<p><img src="https://upload-images.jianshu.io/upload_images/7041675-d38f9817410a015c.gif?imageMogr2/auto-orient/strip" alt="移动有空白.gif"></p>
<p>&emsp;&emsp;尼玛，什么鬼？动是动起来了，为啥左边还有一段空白？不要急，想一想为什么？我们之前是从Y轴开始画的，我们这个动画是从左向又移动一个endx的值，所以，我们设置起点的时候，也向左偏移一个endx的值不就好了么？我们再试一试</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">  protected void onDraw(Canvas canvas) &#123;</span><br><span class="line">    super.onDraw(canvas);</span><br><span class="line">    path.reset();</span><br><span class="line">//    path.moveTo(0, 300);</span><br><span class="line">//    path.moveTo(0, startY);</span><br><span class="line">    path.moveTo(-endX + currentStartX, startY);</span><br><span class="line">//    path.quadTo(150, 150, 300, 300);</span><br><span class="line">//    path.quadTo(450, 450, 600, 300);</span><br><span class="line">    //这里的for循环为什么每次要加2倍的终点x坐标呢？</span><br><span class="line">    //你想一想，我们一次for循环，画的图的终点x坐标在哪？</span><br><span class="line">    for (int i = -endX; i &lt; getWidth() + endX; i += 2 * endX) &#123;</span><br><span class="line">//      path.rQuadTo(150, -150, 300, 0);</span><br><span class="line">//      path.rQuadTo(150, 150, 300, 0);</span><br><span class="line">      path.rQuadTo(endX / 2, -controlY, endX, 0);</span><br><span class="line">      path.rQuadTo(endX / 2, controlY, endX, 0);</span><br><span class="line">    &#125;</span><br><span class="line">    canvas.drawPath(path, paint);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>跑出来的效果图如下：</p>
<p><img src="https://upload-images.jianshu.io/upload_images/7041675-f84524374a6c4273.gif?imageMogr2/auto-orient/strip" alt="移动会闪一下.gif"></p>
<p>&emsp;&emsp;咦，满脸的嫌弃，这是什么东西啊，空白虽然没了，为什么会卡一下，并且这个也不是我们想要的效果。我们再想一想，我们这个无线循环的动画的原理是什么？ <strong><em>敲黑板，其实，我们就是多画了一个正弦波形，我们移动之后，跟移动之前一样，也就是位移了两个正弦图，结束后的图形，跟结束前的图形重合，然后一直重复动画，从而让用户感觉是无线循环的动画。</em></strong> 辣么，哪里出问题呢？想一想，为什么达不到我们的效果，肯定是我们水平移动距离的有问题啊。找啊找啊找，找到了，我们这里的endx坐标，是一个完整正弦图形的一半。所以，我们动画移动的距离要乘以2。如下：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">currentStartX = (int) (2 * endX * animatedValue);//动画里面的</span><br><span class="line">//动画还要加上插值器，从而达到平滑的效果</span><br><span class="line">animator.setInterpolator(new LinearInterpolator());</span><br><span class="line"></span><br><span class="line">辣么，这里距离变了，我们起始点的距离，循环的距离也要变。要不然会有空白</span><br><span class="line">path.moveTo(-endX*2 + currentStartX, startY);</span><br><span class="line"></span><br><span class="line">for (int i = -endX*2; i &lt; getWidth() + endX*2; i += 2 * endX) &#123;...&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;综上所述，去除无关代码之后的完整代码，如下：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Author: 海晨忆</span><br><span class="line"> * Date: 2018/3/27</span><br><span class="line"> * Desc:</span><br><span class="line"> */</span><br><span class="line">public class WaveView1 extends View &#123;</span><br><span class="line">  private Path path;</span><br><span class="line">  private Paint paint;</span><br><span class="line">  private Paint paint1;</span><br><span class="line"></span><br><span class="line">  private int startY = 300;</span><br><span class="line"></span><br><span class="line">  private int endX = 300;</span><br><span class="line"></span><br><span class="line">  private int controlY = 150;</span><br><span class="line"></span><br><span class="line">  private int currentStartX;</span><br><span class="line"></span><br><span class="line">  public WaveView1(Context context) &#123;</span><br><span class="line">    this(context, null);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  public WaveView1(Context context, @Nullable AttributeSet attrs) &#123;</span><br><span class="line">    this(context, attrs, 0);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  public WaveView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) &#123;</span><br><span class="line">    super(context, attrs, defStyleAttr);</span><br><span class="line">    initView();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  private void initView() &#123;</span><br><span class="line">    path = new Path();</span><br><span class="line">    paint = new Paint();</span><br><span class="line">    paint1 = new Paint();</span><br><span class="line">    paint.setColor(Color.GREEN);</span><br><span class="line">    paint.setStyle(Paint.Style.STROKE);</span><br><span class="line">    paint.setStrokeWidth(10);</span><br><span class="line">    paint1.set(paint);</span><br><span class="line">    paint1.setColor(Color.RED);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @Override</span><br><span class="line">  protected void onDraw(Canvas canvas) &#123;</span><br><span class="line">    super.onDraw(canvas);</span><br><span class="line">    path.reset();</span><br><span class="line">    path.moveTo(-endX*2 + currentStartX, startY);</span><br><span class="line">    for (int i = -endX*2; i &lt; getWidth() + endX*2; i += 2 * endX) &#123;</span><br><span class="line">      path.rQuadTo(endX / 2, -controlY, endX, 0);</span><br><span class="line">      path.rQuadTo(endX / 2, controlY, endX, 0);</span><br><span class="line">    &#125;</span><br><span class="line">    canvas.drawPath(path, paint);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  public void startAnimation() &#123;</span><br><span class="line">    ValueAnimator animator = ValueAnimator.ofFloat(0, 1);</span><br><span class="line">    animator.setDuration(1000);</span><br><span class="line">    animator.setRepeatCount(ValueAnimator.INFINITE);</span><br><span class="line">    animator.setInterpolator(new LinearInterpolator());</span><br><span class="line">    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() &#123;</span><br><span class="line">      @Override</span><br><span class="line">      public void onAnimationUpdate(ValueAnimator animation) &#123;</span><br><span class="line">        Float animatedValue = (Float) animation.getAnimatedValue();</span><br><span class="line">        currentStartX = (int) (2 * endX * animatedValue);</span><br><span class="line">        postInvalidate();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    animator.start();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>跑出来的效果图如下：</p>
<p><img src="https://upload-images.jianshu.io/upload_images/7041675-aefa92a1ca8a1026.gif?imageMogr2/auto-orient/strip" alt="移动水波纹.gif"></p>
<p>完美达到了我们的预期效果。</p>
<h2 id="第三步：自定义View上下移动"><a href="#第三步：自定义View上下移动" class="headerlink" title="第三步：自定义View上下移动"></a>第三步：自定义View上下移动</h2><p>&emsp;&emsp;经过上面的左右动画，现在这个上下移动的动画就很简单了，很明显是改变起始点y坐标的值，当然，肯定是属性动画。代码如下：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">public void startAnimation() &#123;</span><br><span class="line">    </span><br><span class="line">    ...//这是我们的左移动画，没写上来</span><br><span class="line">    </span><br><span class="line">    //这就是我们的竖着移动的动画</span><br><span class="line">    ValueAnimator animator1 = ValueAnimator.ofFloat(0, 1);</span><br><span class="line">    animator1.setDuration(5000);</span><br><span class="line">    animator1.setInterpolator(new LinearInterpolator());</span><br><span class="line">    animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() &#123;</span><br><span class="line">      @Override</span><br><span class="line">      public void onAnimationUpdate(ValueAnimator animation) &#123;</span><br><span class="line">        Float animatedValue = (Float) animation.getAnimatedValue();</span><br><span class="line">        currentStartY = (int) (getHeight() * animatedValue);</span><br><span class="line">        postInvalidate();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    animator1.start();</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  //改变我们的初始点的y坐标。不要只写个currentStartY，</span><br><span class="line">  //光写这个是从下往上移动，你要的是从上往下移动</span><br><span class="line">  path.moveTo(-endX*2 + currentStartX, getHeight()-currentStartY);</span><br><span class="line">  </span><br><span class="line">  //再就是修改画笔为填充</span><br><span class="line">  paint.setStyle(Paint.Style.FILL_AND_STROKE);</span><br><span class="line">  </span><br><span class="line">  //并且把path连接成一个闭合图形</span><br><span class="line">  ...//这里是onDraw里面的for循环画正弦图形</span><br><span class="line">  path.lineTo(getWidth(),getHeight());</span><br><span class="line">    path.lineTo(0,getHeight());</span><br><span class="line">    path.close();</span><br><span class="line">    canvas.drawPath(path, paint);</span><br></pre></td></tr></table></figure></div>
<p>跑出来的效果图，如下：</p>
<p><img src="https://upload-images.jianshu.io/upload_images/7041675-54f3ea96e5dc33a5.gif?imageMogr2/auto-orient/strip" alt="上下移动有bug.gif"></p>
<p>&emsp;&emsp;到这个位置，基本上已经完成了百分之九十了。我们可以看到开始会有一个问题，结束的时候也有一个问题，这个问题是怎么产生的呢？</p>
<h2 id="第四步：优化开始和结束的动画"><a href="#第四步：优化开始和结束的动画" class="headerlink" title="第四步：优化开始和结束的动画"></a>第四步：优化开始和结束的动画</h2><p>&emsp;&emsp;其实，我们可以想一想，一开始，我们这个控制点的Y值，不应该一出来就是写死的，显得太突兀了，一开始，我们应该是慢慢涨，涨到我们规定的值，然后快结束的时候，我们应该是慢慢减，减到0为止。应该是这样才对。</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">  protected void onDraw(Canvas canvas) &#123;</span><br><span class="line">    super.onDraw(canvas);</span><br><span class="line">    path.reset();</span><br><span class="line">    int currentControlY = 0;</span><br><span class="line">    if (currentStartY &lt; controlY) &#123;</span><br><span class="line">      currentControlY = currentStartY;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      currentControlY = controlY;</span><br><span class="line">    &#125;</span><br><span class="line">    if (getHeight() - currentStartY &lt; controlY) &#123;</span><br><span class="line">      currentControlY = getHeight() - currentStartY;</span><br><span class="line">    &#125;</span><br><span class="line">    path.moveTo(-endX * 2 + currentStartX, getHeight() - currentStartY);</span><br><span class="line">    for (int i = -endX * 2; i &lt; getWidth() + endX * 2; i += 2 * endX) &#123;</span><br><span class="line">      path.rQuadTo(endX / 2, -currentControlY, endX, 0);</span><br><span class="line">      path.rQuadTo(endX / 2, currentControlY, endX, 0);</span><br><span class="line">    &#125;</span><br><span class="line">    path.lineTo(getWidth(), getHeight());</span><br><span class="line">    path.lineTo(0, getHeight());</span><br><span class="line">    path.close();</span><br><span class="line">    canvas.drawPath(path, paint);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>效果图如下：</p>
<p><img src="https://upload-images.jianshu.io/upload_images/7041675-bb761f08bb5704c5.gif?imageMogr2/auto-orient/strip" alt="最终效果图.gif"></p>
<p>经过上面的操作，就完美的达到了我们的预期效果。(PS：把画笔的宽度去掉)</p>
<p>把这个自定义View优化一下，把方法封装好了。<a href="https://download.csdn.net/download/qq_27634797/10312090" target="_blank" rel="noopener">项目链接</a></p>

      
    </div>
    
      <footer class="article-footer">
        完
      </footer>
    
  </div>
  
    
<nav id="article-nav">
  <div class="article-nav-block">
    
      <a href="/2018/05/13/Android——PorterDuffXfermode/" id="article-nav-newer" class="article-nav-link-wrap">
        <strong class="article-nav-caption"></strong>
        <div class="article-nav-title">
          
            Android——PorterDuffXfermode
          
        </div>
      </a>
    
  </div>
  <div class="article-nav-block">
    
      <a href="/2018/05/13/Android自定义刷新布局——Ultra-Pull-To-Refresh-With-Load-More（二））/" id="article-nav-older" class="article-nav-link-wrap">
        <div class="article-nav-title">Android自定义刷新布局——Ultra-Pull-To-Refresh-With-Load-More（二））</div>
        <strong class="article-nav-caption"></strong>
      </a>
    
  </div>
</nav>

    
  
  
</article>
</section>
        <aside id="sidebar">
  
    <div class="widget-box">
  <div class="avatar-box avatar-item">
    <img class="avatar" src="/uploads/artistic_image/head.jpg" title="头像来自网络"></img>
    <h3 class="avatar-name">
      
        海晨忆
      
    </h3>
    <p class="avatar-slogan">
      先谋生，再谋爱。人间值得，未来可期。
    </p>
  </div>
</div>


  
    
  <div class="widget-box">
    <h3 class="widget-title">分类</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Android-Socket/">Android -Socket</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-加密算法/">Android -加密算法</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-常用功能/">Android -常用功能</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-框架/">Android -框架</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-源码解析/">Android -源码解析</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-自定义view/">Android -自定义view</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-设计模式/">Android -设计模式</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/JAVA-并发/">JAVA -并发</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Java-SpringBoot/">Java -SpringBoot</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Tomcat/">Tomcat</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/kotlin/">kotlin</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/数据库-JDBC/">数据库 -JDBC</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/数据库-MySQL/">数据库 -MySQL</a></li></ul>
    </div>
  </div>


  
    

  
    
  
    
  <div class="widget-box">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/07/">July 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/11/">November 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/10/">October 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/09/">September 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/08/">August 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/07/">July 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/06/">June 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/10/">October 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/05/">May 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/03/">March 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/02/">February 2018</a></li></ul>
    </div>
  </div>

  
    
  <div class="widget-box">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2021/07/27/JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁/">JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁</a>
          </li>
        
          <li>
            <a href="/2021/07/19/JAVA—线程同步器AQS/">JAVA—线程同步器AQS</a>
          </li>
        
          <li>
            <a href="/2021/07/07/Android模拟点击/">Android模拟点击</a>
          </li>
        
          <li>
            <a href="/2019/11/27/SpringBoot系列-消息-RabbitMQ-（二十）/">SpringBoot系列-消息(RabbitMQ)（二十）</a>
          </li>
        
          <li>
            <a href="/2019/11/13/SpringBoot系列-Docker（十九）/">SpringBoot系列-Docker（十九）</a>
          </li>
        
      </ul>
    </div>
  </div>

  
      <div class="widget-box">
    <h3 class="widget-title">友情链接</h3>
    <div class="widget">
      
        <a class="hrf" style="display: block;" href="https://github.com/haichenyi" title target='_blank'
        >Github</a>
      
        <a class="hrf" style="display: block;" href="https://www.jianshu.com/u/6077ee440c37" title target='_blank'
        >简书</a>
      
        <a class="hrf" style="display: block;" href="https://blog.csdn.net/qq_27634797" title target='_blank'
        >CSDN</a>
      
    </div>
  </div>

  
</aside>
      </div>
      <footer id="footer">
  <div class="foot-box footers global-width">
    &copy;2017-2021 海晨忆 &nbsp;&nbsp;
    <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span id="busuanzi_container_site_pv">阁下是第<span id="busuanzi_value_site_pv"></span>个访客</span>
  </div>
</footer>
      <script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
if (!window.jQuery) {
var script = document.createElement('script');
script.src = "/js/jquery-2.0.3.min.js";
document.body.write(script);
}
</script>

  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>



    </div>
    <nav id="mobile-nav" class="mobile-nav-box">
  <div class="mobile-nav-img mobile-nav-top"></div>
  
    <a href="/" class="mobile-nav-link">首页</a>
  
    <a href="/archives" class="mobile-nav-link">归档</a>
  
    <a href="/about" class="mobile-nav-link">简历</a>
  
  <div class="mobile-nav-img  mobile-nav-bottom"></div>
</nav>    
  </div>
</body>
</html>